package lk.icta.core.service.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.ELandRuntimeException;
import lk.icta.common.ErrorCodes;
import lk.icta.common.service.DocumentManagementService;
import lk.icta.common.service.MailSenderService;
import lk.icta.common.service.SmsSenderService;
import lk.icta.common.util.CommonUtil;
import lk.icta.core.dao.AlienationRailwayDAO;
import lk.icta.core.dao.LandRailwayDAO;
import lk.icta.core.entity.*;
import lk.icta.core.service.AlienationRailwayManagementService;
import lk.icta.core.service.LandRailwayManagementService;
import lk.icta.user.common.Permission;
import lk.icta.user.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class LandRailwayManagementServiceImpl implements LandRailwayManagementService {

    private static final Logger logger = LoggerFactory.getLogger(LandRailwayManagementServiceImpl.class);

    private final LandRailwayDAO landRailwayDAO;
    private final AlienationRailwayDAO alienationRailwayDAO;
    private final AlienationRailwayManagementService alienationRailwayManagementService;
    private final DocumentManagementService documentManagementService;
    private final MailSenderService mailSenderService;
    private final SmsSenderService smsSenderService;

    public LandRailwayManagementServiceImpl(LandRailwayDAO landRailwayDAO, AlienationRailwayDAO alienationRailwayDAO,
                                            AlienationRailwayManagementService alienationRailwayManagementService,
                                            DocumentManagementService documentManagementService,
                                            MailSenderService mailSenderService,
                                            SmsSenderService smsSenderService) {
        this.landRailwayDAO = landRailwayDAO;
        this.alienationRailwayDAO = alienationRailwayDAO;
        this.alienationRailwayManagementService = alienationRailwayManagementService;
        this.documentManagementService = documentManagementService;
        this.mailSenderService = mailSenderService;
        this.smsSenderService = smsSenderService;
    }


    @Transactional(propagation = Propagation.REQUIRED)
    public void addLand(LandRailway land, AlienationRailway alienationRailway, User user, String language) {
        logger.debug("Adding land {} by {}", land.getLandFileNumber(), user.getUsername());
        if (user.isAuthorized(Permission.INITIAL_LAND_DATA_ENTRY)) {

            validateRailwayDetail(land, alienationRailway);
            setLandLogComment(land, "Added land ", user);

            land.setState(LandRailway.State.DATA_ENTRY);
            land.setMigrationState(LandRailway.MigrationState.NONE);
            if (user.getBranch() != null) {
                land.setBranch(user.getBranch());
            }

            //save land
            landRailwayDAO.addLand(land, user);
            if (land.getDocumentList() != null) {
                try {
                    documentManagementService.uploadfiles(user, land.getDocumentList(), land.getId());
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }
            LeaseDetail ld = alienationRailway.getLeaseDetail();
            ld.setTotalPayable(ld.getArrearsAmount() + ld.getPaidAmount());
            ld.setTotalTaxPayable(ld.getArrearsTax() + ld.getPaidTax());
            alienationRailway.setLeaseDetail(ld);
            //save Alienation

            ArrearsHistoryRecord arrearsHistoryRecord = new ArrearsHistoryRecord();
            arrearsHistoryRecord.setYear(Calendar.getInstance().get(Calendar.YEAR));
            arrearsHistoryRecord.setActiveRecord(true);
            // Assumption : All the arrears calculated up to date has been entered under arrears
            arrearsHistoryRecord.setArrearsAmount(ld.getAnnualRental()+ld.getAnnualTax());

            List<ArrearsHistoryRecord> ahRecords = new ArrayList<ArrearsHistoryRecord>();
            ahRecords.add(arrearsHistoryRecord);
            alienationRailway.setArrearsHistoryRecords(ahRecords);

            alienationRailway.setLand(land);
            alienationRailway.getLeaseDetail().setLeaseNo(alienationRailway.getReferenceNo());
            alienationRailway.getValuation().setLand(land);
            alienationRailway.setMigrationState(AlienationRailway.MigrationState.NONE);

            alienationRailwayManagementService.addAlienation(alienationRailway, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add land",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Override
    public LandRailway getLandByIdUKey(long idUKey, User user) {
        logger.debug("getLandByIdUKey {}", idUKey);
        LandRailway land = landRailwayDAO.getLandByIdUKey(idUKey, user);

        //if (AppConstants.ADMIN.equals(land.getLifeCycleInfo().getCreatedUser().getUsername())) {
        //land.setMigratedRecord(true);
        //}
        return land;
    }


    @Transactional(propagation = Propagation.REQUIRED)
    public void approveLandInformation(LandRailway land, String approvalComment, User user) {
        logger.debug("Approving land {} by {}", land.getId(), user.getUsername());
        if (!user.isAuthorized(Permission.LAND_APPROVAL)) {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to approve land information",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        } else {
            LandRailway existing = landRailwayDAO.getLandByIdUKey(land.getId(), user);
            AlienationRailway existingAlienation = alienationRailwayManagementService.getActiveAlienationByLandId(existing.getId(), user);

            if (existing.getVersion() != land.getVersion()) {
                handleELandRuntimeException("The record already modified", ErrorCodes.ILLEGAL_STATE);
                return;
            }

            if (LandRailway.State.ARCHIVED.equals(existing.getState())) {
                handleELandRuntimeException("Attempt to approve archived land information. [Land Name: " +
                        land.getLandFileNumber() + "]", ErrorCodes.ARCHIVED_LAND_APPROVAL);
            } else if (LandRailway.State.APPROVED.equals(existing.getState())) {
                handleELandRuntimeException("Attempt to approve already approved land " + existing.getId(),
                        ErrorCodes.APPROVED_LAND_APPROVAL);
            }/* else if (LandRailway.State.REJECTED.equals(existing.getState())) {
                handleELandRuntimeException("Attempt to approve already rejected land " + existing.getId(),
                        ErrorCodes.REJECTED_LAND_APPROVAL);
            }*/ else if (LandRailway.State.DATA_ENTRY.equals(existing.getState())) {

                if (user.getRole().getId().equals(AppConstants.BO_USER_ROLE)) {
                    existing.setState(LandRailway.State.PENDING);
                    existingAlienation.setApproveState(AlienationRailway.State.APPROVED_BY_BO);

                    //set Approval Comment
                    setApprovalComment(existing, approvalComment, true, user);

                    landRailwayDAO.updateLand(existing, user);
                    alienationRailwayDAO.updateAlienation(existingAlienation, user);
                }


            } else if (LandRailway.State.PENDING.equals(existing.getState()) || LandRailway.State.REJECTED.equals(existing.getState())) {
                logger.debug("Approve land {} by {}", land.getLandFileNumber(), user.getUsername());
                if (user.getRole().getId().equals(AppConstants.SC_USER_ROLE)) {
                    existing.setState(LandRailway.State.DATA_ENTRY);
                    existingAlienation.setApproveState(AlienationRailway.State.DATA_ENTRY);
                } else if (user.getRole().getId().equals(AppConstants.BO_USER_ROLE)) {
                    existing.setState(LandRailway.State.PENDING);
                    existingAlienation.setApproveState(AlienationRailway.State.APPROVED_BY_BO);

                } else if (user.getRole().getId().equals(AppConstants.SH_USER_ROLE)) {
                    existing.setState(LandRailway.State.PENDING);
                    existingAlienation.setApproveState(AlienationRailway.State.APPROVED_BY_SH);
                } else if (user.getRole().getId().equals(AppConstants.SDH_USER_ROLE)) {
                    existing.setState(LandRailway.State.PENDING);
                    existingAlienation.setApproveState(AlienationRailway.State.APPROVED_BY_SDH);
                } else if (user.getRole().getId().equals(AppConstants.DH_USER_ROLE)) {
                    existing.setState(LandRailway.State.APPROVED);
                    existingAlienation.setApproveState(AlienationRailway.State.APPROVED_BY_DH);
                }


                //set Approval Comment
                setApprovalComment(existing, approvalComment, true, user);

                landRailwayDAO.updateLand(existing, user);
                alienationRailwayDAO.updateAlienation(existingAlienation, user);

            } else {
                handleELandRuntimeException("Unhandled error occurred", ErrorCodes.ILLEGAL_STATE);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void rejectLandInformation(LandRailway land, String approvalComment, User user) {

        logger.debug("Rejecting land {} by {}", land.getId(), user.getUsername());
        if (!user.isAuthorized(Permission.LAND_APPROVAL)) {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to reject land information",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
            return;
        }

        LandRailway existing = landRailwayDAO.getLandByIdUKey(land.getId(), user);
        AlienationRailway existingAlienation = alienationRailwayManagementService.getActiveAlienationByLandId(existing.getId(), user);


        if (existing.getVersion() != land.getVersion()) {
            handleELandRuntimeException("The record already modified", ErrorCodes.ILLEGAL_STATE);
            return;
        }

        if (LandRailway.State.ARCHIVED.equals(existing.getState())) {
            handleELandRuntimeException("Attempt to reject archived land information. [Land Name: " +
                    land.getLandFileNumber() + "]", ErrorCodes.ARCHIVED_LAND_REJECTION);
        } else if (LandRailway.State.APPROVED.equals(existing.getState())) {
            handleELandRuntimeException("Attempt to reject already approved land " + existing.getId(),
                    ErrorCodes.APPROVED_LAND_REJECTION);
        } /*else if (LandRailway.State.REJECTED.equals(existing.getState())) {
            handleELandRuntimeException("Attempt to reject already rejected land " + existing.getId(),
                    ErrorCodes.REJECTED_LAND_REJECTION);
        }*/ else if (LandRailway.State.DATA_ENTRY.equals(existing.getState())) {
            if (user.getRole().getId().equals(AppConstants.BO_USER_ROLE)) {
                existing.setState(LandRailway.State.REJECTED);
                existingAlienation.setApproveState(AlienationRailway.State.REJECTED_BY_BO);
            }

        } else if (LandRailway.State.PENDING.equals(existing.getState()) || LandRailway.State.REJECTED.equals(existing.getState())) {
            logger.debug("Reject land information. [Land Name: {}]", land.getLandFileNumber());

            if (user.getRole().getId().equals(AppConstants.BO_USER_ROLE)) {
                existing.setState(LandRailway.State.REJECTED);
                existingAlienation.setApproveState(AlienationRailway.State.REJECTED_BY_BO);
            } else if (user.getRole().getId().equals(AppConstants.SH_USER_ROLE)) {
                existing.setState(LandRailway.State.REJECTED);
                existingAlienation.setApproveState(AlienationRailway.State.REJECTED_BY_SH);
            } else if (user.getRole().getId().equals(AppConstants.SDH_USER_ROLE)) {
                existing.setState(LandRailway.State.REJECTED);
                existingAlienation.setApproveState(AlienationRailway.State.REJECTED_BY_SDH);
            } else if (user.getRole().getId().equals(AppConstants.DH_USER_ROLE)) {
                existing.setState(LandRailway.State.REJECTED);
                existingAlienation.setApproveState(AlienationRailway.State.REJECTED_BY_DH);
            }


            //set Approval Comment
            setApprovalComment(existing, approvalComment, false, user);

            landRailwayDAO.updateLand(existing, user);
            alienationRailwayDAO.updateAlienation(existingAlienation, user);


        } else {
            handleELandRuntimeException("Unhandled error occurred", ErrorCodes.ILLEGAL_STATE);
        }

    }

    private void setApprovalComment(LandRailway existing, String approvalComment, boolean approve, User user) {

        String approveStr = "";
        if (approve) {
            approveStr = "Approved";
        } else {
            approveStr = "Rejected";
        }
        //Stop creating empty new lines to log
        if (approvalComment != null && !("".equals(approvalComment.trim()))) {
            approvalComment = approvalComment + "\n";
        }

        if (existing.getApprovalNote() != null) {
            String approvalNote = existing.getApprovalNote();
            if (!("".equals(approvalNote.trim()))) {
                approvalNote = approvalNote + "\n";
            }
            //existing.setApprovalNote(existing.getApprovalNote() + "\n" + approvalComment + "\n" + approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + "\n");
            existing.setApprovalNote(approvalNote + approvalComment + approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        } else {
            existing.setApprovalNote(approvalComment + approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        }

    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateLand(LandRailway land, AlienationRailway alienationRailway, User user, String language) {
        logger.debug("Adding land {} by {}", land.getLandFileNumber(), user.getUsername());
        if (user.isAuthorized(Permission.INITIAL_LAND_DATA_ENTRY)) {

            LandRailway existing = this.getLandByIdUKey(land.getId(), user);

            land.setState(LandRailway.State.DATA_ENTRY);
            land.setMigrationState(existing.getMigrationState());

            if (existing.getBranch().getId() == AppConstants.BRANCH_SYS) {
                land.setBranch(user.getBranch());
            } else {
                land.setBranch(existing.getBranch());
            }

            LeaseDetail ld = alienationRailway.getLeaseDetail();
            ld.setTotalPayable(ld.getArrearsAmount() + ld.getPaidAmount());
            ld.setTotalTaxPayable(ld.getArrearsTax() + ld.getPaidTax());
            alienationRailway.setLeaseDetail(ld);

            land.getLifeCycleInfo().setCreatedTimeStamp(existing.getLifeCycleInfo().getCreatedTimeStamp());
            land.getLifeCycleInfo().setCreatedUser(existing.getLifeCycleInfo().getCreatedUser());

            validateRailwayDetail(land, alienationRailway);
            setLandLogComment(land, "Updated land ", user);
            //update  land
            landRailwayDAO.updateLand(land, user);
            if (land.getDocumentList() != null) {
                try {
                    documentManagementService.uploadfiles(user, land.getDocumentList(), land.getId());
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }
            //save Alienation
            AlienationRailway exist_alienation  = alienationRailwayManagementService.getActiveAlienationByLandId(land.getId(), user);
            List<ArrearsHistoryRecord> ahRecords = exist_alienation.getArrearsHistoryRecords();
            ArrearsHistoryRecord ahRecordCurrYear = null;
            /**
             * IF AH record list available for particular alienation
             * ELSE
             *
             */
            if(ahRecords.size() > 0){                                                                      // Create new Arrears Age Record for alienation if record is null
                ahRecordCurrYear = getCurrentYearArrearsRecord(ahRecords);
                if(ahRecordCurrYear == null){
                    ahRecordCurrYear = new ArrearsHistoryRecord();
                    ahRecordCurrYear.setYear(Calendar.getInstance().get(Calendar.YEAR));
                    ahRecordCurrYear.setArrearsAmount(ld.getAnnualRental()+ld.getAnnualTax());
                    ahRecordCurrYear.setActiveRecord(true);
                }
                    //aaRecord.addToCurrentYearArrears(ld.getArrearsAmount());
                /*else if(ld.getArrearsAmount() != exist_alienation.getLeaseDetail().getArrearsAmount()){   // Update ArrearsHistoryRecord if Arrears amounts have changed
                    //aaRecord.setCurrentYearArrears(ld.getArrearsAmount());
                }*/
            }else{
                ahRecordCurrYear = new ArrearsHistoryRecord();
                ahRecordCurrYear.setYear(Calendar.getInstance().get(Calendar.YEAR));
                ahRecordCurrYear.setActiveRecord(true);
                // Assumption : All the arrears calculated up to date has been entered under arrears
                // All Arrears = Past Year's Arrears + Current Year Unpaid Rental and taxes
                ahRecordCurrYear.setArrearsAmount(ld.getArrearsAmount()+ld.getArrearsTax());
                ahRecords = new ArrayList<ArrearsHistoryRecord>();

            }
            ahRecords.add(ahRecordCurrYear);
            alienationRailway.setArrearsHistoryRecords(ahRecords);
            //alienationRailway.setArrearsHistoryRecord(aaRecord);

            alienationRailway.setLand(land);
            alienationRailway.getLeaseDetail().setLeaseNo(alienationRailway.getReferenceNo());
            alienationRailway.getValuation().setLand(land);

            Calendar cal = Calendar.getInstance();
            cal.setTime(alienationRailway.getLeaseDetail().getLeaseStartingDate());
            cal.add(Calendar.YEAR, alienationRailway.getLeaseDetail().getValidityPeriod());
            alienationRailway.getLeaseDetail().setLeaseExpiringDate(cal.getTime());

            //calculate Payment Data

//            if (calculatePaymentDetails(alienationRailway)) {
            alienationRailwayManagementService.updateAlienation(alienationRailway, user);
//            } else {
//                handleELandRuntimeException(user.getUsername() + " Invalid Data ",
//                        ErrorCodes.INVALID_DATA);
//            }

        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add land",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    private ArrearsHistoryRecord getCurrentYearArrearsRecord(List<ArrearsHistoryRecord> ahRecords) {
        int cYear = Calendar.getInstance().get(Calendar.YEAR);
        ArrearsHistoryRecord ahr = null;
        for(ArrearsHistoryRecord ah : ahRecords){
            if(ah.getYear() == cYear){
                ahr = ah;
            }
        }
        return ahr;
    }

    public void validateRailwayDetail(LandRailway land, AlienationRailway alienationRailway) {
        logger.debug("validateRailwayDetail  ");
        if (land.getDistrict() == null || land.getDistrict().getId() == 0 ||
                land.getRailwayTrack() == null || land.getRailwayTrack().getId() == 0 ||
                land.getIpwZone() == null || land.getIpwZone().getId() == 0 ||
                land.getDistrictEngineerZone() == null || land.getDistrictEngineerZone().getId() == 0 ||
                isEmptyString(land.getLandFileNumber()) ||
                isEmptyString(alienationRailway.getReferenceNo())) {
            logger.debug("handleELandRuntimeException");
            handleELandRuntimeException("Railway Land record being processed is incomplete " +
                    "Check required field values", ErrorCodes.INVALID_DATA);
        }
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<LandRailway> searchDetailedLandsOfDSDivisionBySearchText(String fileNo, String planNo, String lotNo, String holderName, User user, int pageNo) {
        logger.debug("Filter All lands of DSDivision {} by land name: {}", user.getUsername(), fileNo);
        return landRailwayDAO.searchDetailedLandsOfDSDivisionBySearchText(fileNo, planNo, lotNo, holderName, user, pageNo);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Object[]> getLandStats(int id, String searchBy, LandRailway.State landState, Date fromDate,
                                       Date toDate, LandRailway.MigrationState migState) {
        return landRailwayDAO.getAllLandStatistics(id, searchBy, landState, fromDate, toDate, migState);

    }

    @Override
    public List<LandRailway> getLandStatisticsReportData(int branchId, int trackId, int districtId, Date fromDate, Date toDate, int lState) {
        LandRailway.State migState = null;
        if (lState > 0) {
            migState = LandRailway.State.values()[lState - 1];
        }
        return landRailwayDAO.getLandStatisticReport(branchId, trackId, districtId, fromDate, toDate, migState);
    }

    @Override
    public List<LandRailway> getDetailedLandList(int branchId, int railwayTrackId, int districtId, String startingDate, String endingDate, int lState, User user, String language) {
        Date startDate = CommonUtil.getDate(startingDate);
        Date endDate = CommonUtil.getDate(endingDate);
        if(endDate == null) { endDate = new Date(); } // if end date isn't provided get today
        LandRailway.State landState = null;
        if (lState > 0) {
            landState = LandRailway.State.values()[lState - 1];
        }
        return landRailwayDAO.getLandDetailsList(branchId, railwayTrackId, districtId, startDate, endDate, landState);
    }

    @Override
    public List<LandRailway> getLandAvailableLikeFileNo(String landFileNumber, User user) {
        return landRailwayDAO.getLandLikeFileNumber(landFileNumber);
    }


    private static final boolean isEmptyString(String s) {
        return s == null || s.trim().length() == 0;
    }

    protected void setLandLogComment(LandRailway land, String approveStr, User user) {
        if (land.getApprovalNote() != null) {
            land.setApprovalNote(land.getApprovalNote() + "\n" + approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        } else {
            land.setApprovalNote(approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        }
    }

    private void handleELandRuntimeException(String message, int code) {
        logger.error(message);
        throw new ELandRuntimeException(message, code);
    }
}


